package com.youlu.campus.service.statistics;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.google.common.collect.Lists;
import com.youlu.campus.entity.UserPO;
import com.youlu.campus.entity.pay.statistics.ActivityPayPageStatistics;
import com.youlu.campus.entity.pay.statistics.ActivityPayPageHourStatistics;
import com.youlu.campus.entity.pay.statistics.PayButtonStatistics;
import com.youlu.campus.entity.pay.statistics.PayStatisticsConfigInfo;
import com.youlu.campus.entity.pay.statistics.vo.ActivityPayPageDataBoardStatisticsResponse;
import com.youlu.campus.entity.pay.statistics.vo.PayButtonStatisticsQueryReq;
import com.youlu.campus.entity.pay.statistics.vo.StatisticsReq;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFacade;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 收款按钮统计
 */
@Service
@Slf4j
public class PayButtonStatisticsService {

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private MapperFacade mapperFacade;

    @Autowired
    private StringRedisTemplate redisTemplate;

    private static final String USER_ACTIVITY_DATE_UV = "h5:pay:statistics:activity:%s:userId:%s:pageKey:%s:date:%s";

    /**
     * 过期日期  2天
     */
    private final static Integer EXPIRE_SECOND = 86400;

    private final static Integer HOUR_EXPIRE_SECOND = 3600;

    private static final String USER_ACTIVITY_DATE_HOUR_UV = "h5:pay:statistics:activity:%s:userId:%s:pageKey:%s:date:%s";

    private final static String TIME = ":00";

    /**
     * 一天时间
     */
    private final static long DAY_EXPIRE_SECOND = 24 * 60 * 60;

    public List<PayStatisticsConfigInfo> findByActivityId(String activityId) {
        Query query = new Query();
        query.addCriteria(Criteria.where("activityId").is(activityId));
        return this.mongoTemplate.find(query, PayStatisticsConfigInfo.class);
    }

    public List<ActivityPayPageDataBoardStatisticsResponse> dataBoard(PayButtonStatisticsQueryReq queryReq) {
        String activityId = queryReq.getActivityId();
        Date startTime = queryReq.getStartTime();
        Date endTime = queryReq.getEndTime();
        Query query = new Query();
        if (StringUtils.isNotBlank(activityId)) {
            query.addCriteria(Criteria.where("activityId").is(activityId));
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        if (Objects.nonNull(startTime) && Objects.nonNull(endTime)) {
            query.addCriteria(Criteria.where("date").gte(Long.valueOf(sdf.format(startTime) + "00")).lte(Long.valueOf(sdf.format(endTime) + "23")));
        }
        log.info("query:{}",  SerializerFeature.IgnoreNonFieldGetter);
        List<ActivityPayPageHourStatistics> list = this.mongoTemplate.find(query, ActivityPayPageHourStatistics.class);
        if (CollectionUtils.isEmpty(list)) {
            return null;
        }
        List<ActivityPayPageDataBoardStatisticsResponse> responses = Lists.newArrayList();
        //时间分组
        Map<Long, List<ActivityPayPageHourStatistics>> mapDateGroup = list.stream().collect(Collectors.groupingBy(ActivityPayPageHourStatistics::getDate));
        mapDateGroup.forEach((date, item) -> {
            ActivityPayPageDataBoardStatisticsResponse activityPayPageDataBoardStatistics = new ActivityPayPageDataBoardStatisticsResponse();
            activityPayPageDataBoardStatistics.setDate(date);
            ActivityPayPageHourStatistics activityPayPageHourStatistics = item.get(0);
            if (Objects.nonNull(activityPayPageHourStatistics)) {
                activityPayPageDataBoardStatistics.setDateFormat(activityPayPageHourStatistics.getDateFormat());
            }
            activityPayPageDataBoardStatistics.setDataList(item);
            responses.add(activityPayPageDataBoardStatistics);
        });

        responses.forEach(activityPayPageHourStatistics -> {
            activityPayPageHourStatistics.setDateFormat(activityPayPageHourStatistics.getDateFormat() + TIME);
        });

        return responses.stream().sorted(Comparator.comparing(ActivityPayPageDataBoardStatisticsResponse::getDate)).collect(Collectors.toList());
    }

    public List<PayButtonStatistics> list(PayButtonStatisticsQueryReq queryReq) {
        String activityId = queryReq.getActivityId();
        Date startTime = queryReq.getStartTime();
        Date endTime = queryReq.getEndTime();
        Query query = new Query();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        if (Objects.nonNull(startTime) && Objects.nonNull(endTime)) {
            query.addCriteria(Criteria.where("date").gte(Integer.valueOf(sdf.format(startTime))).lte(Integer.valueOf(sdf.format(endTime))));
        }
        query.addCriteria(Criteria.where("activityId").is(activityId));
        List<ActivityPayPageStatistics> activityPayPageStatisticsList = this.mongoTemplate.find(query, ActivityPayPageStatistics.class);
        if (CollectionUtils.isEmpty(activityPayPageStatisticsList)) {
            return null;
        }

        List<PayButtonStatistics> result = Lists.newArrayList();
        Map<String, List<ActivityPayPageStatistics>> mapPayButton = activityPayPageStatisticsList.stream().collect(Collectors.groupingBy(ActivityPayPageStatistics::getPageKey));
        mapPayButton.forEach((buttonKey, item) -> {
            long pv = item.stream().mapToLong(ActivityPayPageStatistics::getPv).sum();
            long uv = item.stream().mapToLong(ActivityPayPageStatistics::getUv).sum();
            Double second = item.stream().mapToDouble(ActivityPayPageStatistics::getSecond).sum();
            PayButtonStatistics payButtonStatistics = new PayButtonStatistics();
            payButtonStatistics.setPv(pv);
            payButtonStatistics.setUv(uv);
            if (pv > 0 && second.intValue() > 0) {
                BigDecimal pvBigDecimal = BigDecimal.valueOf(pv);
                BigDecimal secondBigDecimal = BigDecimal.valueOf(second);
                payButtonStatistics.setSecond(secondBigDecimal.divide(pvBigDecimal, 2, BigDecimal.ROUND_HALF_UP).doubleValue());
            }
            ActivityPayPageStatistics activityPayPageStatistics = item.get(0);
            if (Objects.nonNull(activityPayPageStatistics) && StringUtils.isNotBlank(activityPayPageStatistics.getPageName())) {
                payButtonStatistics.setButtonKey(activityPayPageStatistics.getPageName());
            } else {
                payButtonStatistics.setButtonKey(buttonKey);
            }

            result.add(payButtonStatistics);
        });
        return result.stream().sorted(Comparator.comparing(PayButtonStatistics::getUv).reversed()).collect(Collectors.toList());
    }

    /**
     * 收款统计
     */
    public void doStatistics(StatisticsReq req) {
        UserPO currentUser = req.getCurrentUser();
        String userId = currentUser.getId();
        String activityId = req.getActivityId();
        String pageKey = req.getPageKey();
        String pageName = req.getPageName();
        if (StringUtils.isAnyBlank(pageKey, activityId)) {
            return;
        }
        initPayPageStatistics(req);
        log.info("【收款按钮统计】用户:{}点击了活动:{}按钮:{}类型中:{}", currentUser.getId(), activityId, pageKey, pageName);
        String nowDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        Integer currentDate = Integer.valueOf(nowDate);
        String dateFormat = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        doActivityPayButtonStatistics(activityId, pageKey, pageName, userId, currentDate, dateFormat, req.getSecond());
        String nowDateHour = LocalTime.now().format(DateTimeFormatter.ofPattern("HH"));
        Integer currentDateHour = Integer.valueOf((nowDate + nowDateHour));
        doActivityPayButtonStatisticsHour(activityId, pageKey, pageName, userId, currentDateHour, dateFormat + " " + LocalTime.now().getHour());
    }

    private void initPayPageStatistics(StatisticsReq req) {
        String activityId = req.getActivityId();
        String pageKey = req.getPageKey();
        String pageName = req.getPageName();
        Query query = new Query();
        query.addCriteria(Criteria.where("activityId").is(activityId));
        query.addCriteria(Criteria.where("pageKey").is(pageKey));
        boolean exists = this.mongoTemplate.exists(query, PayStatisticsConfigInfo.class);
        if (exists) {
            return;
        }
        Update update = new Update();
        Date now = new Date();
        update.set("pageName", pageName);
        update.setOnInsert("createdTime", now);
        update.set("updatedTime", now);
        FindAndModifyOptions findAndModifyOptions = new FindAndModifyOptions();
        findAndModifyOptions.upsert(Boolean.TRUE);
        findAndModifyOptions.returnNew(Boolean.TRUE);
        this.mongoTemplate.findAndModify(query, update, findAndModifyOptions, PayStatisticsConfigInfo.class);
    }

    private void doActivityPayButtonStatistics(String activityId, String pageKey, String pageName, String userId,
                                               Integer currentDate, String dateFormat, Double second) {
        String userActivityButtonKeyDate = String.format(USER_ACTIVITY_DATE_UV, activityId, userId, pageKey, currentDate);
        Query query = new Query();
        query.addCriteria(Criteria.where("activityId").is(activityId));
        query.addCriteria(Criteria.where("date").is(currentDate));
        query.addCriteria(Criteria.where("pageKey").is(pageKey));
        ActivityPayPageStatistics activityPayPageStatistics = this.mongoTemplate.findOne(query, ActivityPayPageStatistics.class);
        Update update = new Update();

        // 替换 JedisCluster.exists 为 StringRedisTemplate.hasKey
        Boolean exists = redisTemplate.hasKey(userActivityButtonKeyDate);
        if (!exists) {
            update.inc("uv", 1);
            // 替换 JedisCluster.set 和 expire 为 StringRedisTemplate 对应方法
            redisTemplate.opsForValue().set(userActivityButtonKeyDate, "1");
            redisTemplate.expire(userActivityButtonKeyDate, dayOfNextTime(), TimeUnit.SECONDS);
        }
        if (second.intValue() > 0) {
            update.inc("second", second);
        }
        if (second.intValue() == 0) {
            update.inc("pv", 1);
        }
        if (Objects.nonNull(activityPayPageStatistics)) {
            this.mongoTemplate.updateFirst(query, update, ActivityPayPageStatistics.class);
            return;
        }
        Date now = new Date();
        update.set("dateFormat", dateFormat);
        update.set("pageName", pageName);
        update.setOnInsert("createdTime", now);
        update.set("updatedTime", now);
        FindAndModifyOptions findAndModifyOptions = new FindAndModifyOptions();
        findAndModifyOptions.upsert(Boolean.TRUE);
        findAndModifyOptions.returnNew(Boolean.TRUE);
        this.mongoTemplate.findAndModify(query, update, findAndModifyOptions, ActivityPayPageStatistics.class);
    }

    private void doActivityPayButtonStatisticsHour(String activityId, String pageKey, String pageName, String userId,
                                                   Integer currentDate, String dateFormat) {
        String userActivityButtonKeyDate = String.format(USER_ACTIVITY_DATE_HOUR_UV, activityId, userId, pageKey, currentDate);
        Query query = new Query();
        query.addCriteria(Criteria.where("activityId").is(activityId));
        query.addCriteria(Criteria.where("date").is(currentDate));
        query.addCriteria(Criteria.where("pageKey").is(pageKey));
        ActivityPayPageHourStatistics activityPayPageHourStatistics = this.mongoTemplate.findOne(query, ActivityPayPageHourStatistics.class);
        Update update = new Update();

        // 替换 JedisCluster.exists 为 StringRedisTemplate.hasKey
        Boolean exists = redisTemplate.hasKey(userActivityButtonKeyDate);
        if (!exists) {
            update.inc("uv", 1);
            // 替换 JedisCluster.set 和 expire 为 StringRedisTemplate 对应方法
            redisTemplate.opsForValue().set(userActivityButtonKeyDate, "1");
            redisTemplate.expire(userActivityButtonKeyDate, HOUR_EXPIRE_SECOND, TimeUnit.SECONDS);
        }
        update.inc("pv", 1);
        if (Objects.nonNull(activityPayPageHourStatistics)) {
            this.mongoTemplate.updateFirst(query, update, ActivityPayPageHourStatistics.class);
            return;
        }

        Date now = new Date();
        update.set("dateFormat", dateFormat);
        update.setOnInsert("createdTime", now);
        update.set("pageName", pageName);
        update.set("updatedTime", now);
        FindAndModifyOptions findAndModifyOptions = new FindAndModifyOptions();
        findAndModifyOptions.upsert(Boolean.TRUE);
        findAndModifyOptions.returnNew(Boolean.TRUE);
        this.mongoTemplate.findAndModify(query, update, findAndModifyOptions, ActivityPayPageHourStatistics.class);
    }

    public static void main(String[] args) {
        long now = System.currentTimeMillis();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            long overTime = (now - (sdf.parse(sdf.format(now)).getTime())) / 1000;
            long nextTime = DAY_EXPIRE_SECOND - overTime;
            System.out.println(nextTime);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        System.out.println();
    }

    /**
     * 一天剩下多少秒
     */
    private long dayOfNextTime() {
        LocalTime midnight = LocalTime.MIDNIGHT;
        LocalDate today = LocalDate.now();
        LocalDateTime todayMidnight = LocalDateTime.of(today, midnight);
        LocalDateTime tomorrowMidnight = todayMidnight.plusDays(1);
        return TimeUnit.NANOSECONDS.toSeconds(Duration.between(LocalDateTime.now(), tomorrowMidnight).toNanos());
    }
}